package com.jzo2o.market.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.model.PageResult;
import com.jzo2o.common.utils.*;
import com.jzo2o.market.constants.TabTypeConstants;
import com.jzo2o.market.enums.ActivityStatusEnum;
import com.jzo2o.market.mapper.ActivityMapper;
import com.jzo2o.market.model.domain.Activity;
import com.jzo2o.market.model.domain.Coupon;
import com.jzo2o.market.model.dto.request.ActivityQueryForPageReqDTO;
import com.jzo2o.market.model.dto.response.ActivityInfoResDTO;
import com.jzo2o.market.model.dto.response.SeizeCouponInfoResDTO;
import com.jzo2o.market.service.IActivityService;
import com.jzo2o.market.service.ICouponService;
import com.jzo2o.market.service.ICouponWriteOffService;
import com.jzo2o.mysql.utils.PageUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

import static com.jzo2o.market.constants.RedisConstants.RedisKey.ACTIVITY_CACHE_LIST;
import static com.jzo2o.market.constants.RedisConstants.RedisKey.COUPON_RESOURCE_STOCK;
import static com.jzo2o.market.enums.ActivityStatusEnum.DISTRIBUTING;
import static com.jzo2o.market.enums.ActivityStatusEnum.NO_DISTRIBUTE;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author itcast
 * @since 2023-09-16
 */
@Service
@Slf4j
public class ActivityServiceImpl extends ServiceImpl<ActivityMapper, Activity> implements IActivityService {
    private static final int MILLION = 1000000;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private ICouponService couponService;

    @Resource
    private ICouponWriteOffService couponWriteOffService;

    /**
     * 扣减库存
     * @param id 活动id
     *  如果扣减库存失败抛出异常
     */
    public void deductStock(Long id){
        boolean update = lambdaUpdate()
                .setSql("stock_num = stock_num-1")
                .eq(Activity::getId, id)
                .gt(Activity::getStockNum, 0)
                .update();
        if(!update){
            throw new CommonException("扣减优惠券库存失败，活动id:"+id);
        }
    }

    @Override
    public ActivityInfoResDTO getActivityInfoByIdFromCache(Long id) {
        // 1、从缓存中获取活动信息
        Object activityList = redisTemplate.opsForValue().get(ACTIVITY_CACHE_LIST);
        if (ObjectUtil.isEmpty(activityList)){
            return null;
        }
        // 2、过滤指定活动信息
        List<ActivityInfoResDTO> list = JSONUtil.toList(activityList.toString(), ActivityInfoResDTO.class);
        if (CollectionUtil.isEmpty(list)){
            return null;
        }
        // 3、过滤指定活动
        return list.stream()
                .filter(activityInfoResDTO -> activityInfoResDTO.getId().equals(id))// 根据id过滤
                .findFirst()
                .orElse(null);// 返回第一个匹配的元素，如果没有匹配的元素，则返回null
    }

    @Override
    public void preHeat() {
        //当前时间
        LocalDateTime now = DateUtils.now();

        //查询进行中还未到结束的优惠券活动， 1个月内待开始的优惠券活动
        List<Activity> list = lambdaQuery()
                .le(Activity::getDistributeStartTime, now.plusDays(30))//1个月内即将开始的
                .in(Activity::getStatus, Arrays.asList(NO_DISTRIBUTE.getStatus(), DISTRIBUTING.getStatus()))//查询待开始和进行中的
                .orderByAsc(Activity::getDistributeStartTime)
                .list();
        if (CollUtils.isEmpty(list)) {
            //防止缓存穿透
            list = new ArrayList<>();
        }
        // 2.数据转换
        List<SeizeCouponInfoResDTO> seizeCouponInfoResDTOS = BeanUtils.copyToList(list, SeizeCouponInfoResDTO.class);
        String seizeCouponInfoStr = JsonUtils.toJsonStr(seizeCouponInfoResDTOS);

        // 3.活动列表写入缓存
        redisTemplate.opsForValue().set(ACTIVITY_CACHE_LIST, seizeCouponInfoStr);

        // 将待生效的活动库存写入redis
        list.stream().filter(v->getStatus(v.getDistributeStartTime(),v.getDistributeEndTime(),v.getStatus())==1).forEach(v->{
            redisTemplate.opsForHash().put(String.format(COUPON_RESOURCE_STOCK, v.getId() % 10), v.getId(), v.getStockNum());
        });
        // 对于已生效的活动库存没有同步时再进行同步
        list.stream().filter(v->getStatus(v.getDistributeStartTime(),v.getDistributeEndTime(),v.getStatus())==2).forEach(v->{
            redisTemplate.opsForHash().putIfAbsent(String.format(COUPON_RESOURCE_STOCK, v.getId() % 10), v.getId(), v.getStockNum());
        });
    }

    @Override
    public List<SeizeCouponInfoResDTO> queryForListFromCache(Integer tabType) {
        //从redis查询活动信息
        Object seizeCouponInfoStr = redisTemplate.opsForValue().get(ACTIVITY_CACHE_LIST);
        if (ObjectUtils.isNull(seizeCouponInfoStr)) {
            return CollUtils.emptyList();
        }
        //将json转为List
        List<SeizeCouponInfoResDTO> seizeCouponInfoResDTOS = JsonUtils.toList(seizeCouponInfoStr.toString(), SeizeCouponInfoResDTO.class);
        //根据tabType确定要查询的状态
        int queryStatus = tabType == TabTypeConstants.SEIZING ? ActivityStatusEnum.DISTRIBUTING.getStatus() : ActivityStatusEnum.NO_DISTRIBUTE.getStatus();
        //过滤数据，并设置剩余数量、实际状态
        List<SeizeCouponInfoResDTO> collect = seizeCouponInfoResDTOS.stream().filter(item -> queryStatus == getStatus(item.getDistributeStartTime(), item.getDistributeEndTime(), item.getStatus()))
                .peek(item -> {
                    //剩余数量
                    item.setRemainNum(item.getStockNum());
                    //状态
                    item.setStatus(queryStatus);
                }).collect(Collectors.toList());
        return collect;
    }

    /**
     * 获取状态，
     * 用于xxl或其他定时任务在高性能要求下无法做到实时状态
     *
     * @return
     */
    private int getStatus(LocalDateTime distributeStartTime, LocalDateTime distributeEndTime, Integer status) {
        if (ActivityStatusEnum.NO_DISTRIBUTE.equals(status) &&
                distributeStartTime.isBefore(DateUtils.now()) &&
                distributeEndTime.isAfter(DateUtils.now())) {//待生效状态，实际活动已开始
            return ActivityStatusEnum.DISTRIBUTING.getStatus();
        }else if(ActivityStatusEnum.NO_DISTRIBUTE.equals(status) &&
                distributeEndTime.isBefore(DateUtils.now())){//待生效状态，实际活动已结束
            return ActivityStatusEnum.LOSE_EFFICACY.getStatus();
        }else if (ActivityStatusEnum.DISTRIBUTING.equals(status) &&
                distributeEndTime.isBefore(DateUtils.now())) {//进行中状态，实际活动已结束
            return ActivityStatusEnum.LOSE_EFFICACY.getStatus();
        }
        return status;
    }

    /**
     * 已领取优惠券自动过期任务
     */
    @Override
    public void processExpireCoupon(){
        // 用户领取的优惠券如果到达有效期仍然没有使用自动改为“已失效”
        boolean update = couponService.update(Wrappers.<Coupon>lambdaUpdate()
                .eq(Coupon::getStatus, 1)
                .lt(Coupon::getValidityTime, new Date())// 有效期 <= 当前时间
                .set(Coupon::getStatus, 4));
        if (update){
            log.info("有优惠券作废！");
        }else {
            log.info("暂无优惠券可修改");
        }
    }

    /**
     * 活动状态修改，
     * 1.活动进行中状态修改
     * 2.活动已失效状态修改
     * 1分钟一次
     */
    @Override
    public void updateActivityStatus(){
        // 对于待生效的活动：到达发放开始时间状态改为“进行中”。
        boolean update = update(Wrappers.<Activity>lambdaUpdate()
                .eq(Activity::getStatus, ActivityStatusEnum.NO_DISTRIBUTE.getStatus())
                .le(Activity::getDistributeStartTime, new Date())
                .gt(Activity::getDistributeEndTime, new Date())// 发放开始时间 <= 当前时间
                .set(Activity::getStatus, ActivityStatusEnum.DISTRIBUTING.getStatus()));
        if (update){
            log.info("任务状态修改成功！");
        }else {
            log.info("暂无任务状态可修改");
        }
        //  对于待生效及进行中的活动：到达发放结束时间状态改为“已失效”
        boolean update1 = update(Wrappers.<Activity>lambdaUpdate()
                .in(Activity::getStatus, 1,2)
                .lt(Activity::getDistributeEndTime, new Date())// 发放结束时间 <= 当前时间
                .set(Activity::getStatus, ActivityStatusEnum.LOSE_EFFICACY.getStatus()));
        if (update1){
            log.info("有任务作废！");
        }else {
            log.info("无任务被作废！");
        }
    }

    /**
     * 优惠券活动分页查询接口
     * @param
     * @return
     */
    @Override
    public PageResult<Activity> pageQury(ActivityQueryForPageReqDTO activityQueryForPageReqDTO) {
        Page<Activity> activityPage = PageUtils.parsePageQuery(activityQueryForPageReqDTO, Activity.class);
        LambdaQueryWrapper<Activity> queryWrapper = Wrappers.<Activity>lambdaQuery()
                .eq(ObjectUtil.isNotEmpty(activityQueryForPageReqDTO.getId()),Activity::getId,activityQueryForPageReqDTO.getId())
                .eq(ObjectUtil.isNotEmpty(activityQueryForPageReqDTO.getStatus()),Activity::getStatus, activityQueryForPageReqDTO.getStatus())
                // 模糊匹配名字
                .eq(ObjectUtil.isNotEmpty(activityQueryForPageReqDTO.getType()),Activity::getType, activityQueryForPageReqDTO.getType())
                .like(ObjectUtil.isNotEmpty(activityQueryForPageReqDTO.getName()),Activity::getName, activityQueryForPageReqDTO.getName())
                .eq(Activity::getIsDeleted, 0);
        Page<Activity> serveTypePage = baseMapper.selectPage(activityPage,queryWrapper);

        return PageUtils.toPage(serveTypePage, Activity.class);
    }
}
